import os
import sys
import time
import socket
import subprocess
import argparse
import json
import math
import numpy as np
import warnings
import importlib
warnings.filterwarnings('ignore', category=FutureWarning)

# 检查端口是否被占用
def check_port_available(port, ip='127.0.0.1'):
    """检查指定端口是否可用"""
    try:
        # 尝试创建一个socket并绑定到指定端口
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(0.1)  # 设置超时
        s.bind((ip, port))
        s.close()
        return True
    except socket.error:
        return False

# 管理端口使用情况
def manage_port_usage(dir_parent_1, action='check', p_port=None, m_port=None):
    log_file = os.path.join(os.path.dirname(dir_parent_1), "port_usage.log")
    
    if action == 'check':
        # 检查日志中已经被占用的端口
        occupied_ports = []
        if os.path.exists(log_file):
            try:
                with open(log_file, 'r') as f:
                    for line in f:
                        parts = line.strip().split(',')
                        if len(parts) >= 3:  # 确保行格式正确
                            python_port = int(parts[1])
                            matlab_port = int(parts[2])
                            occupied_ports.extend([python_port, matlab_port])
            except Exception as e:
                print(f"读取端口日志时出错: {e}")
        return occupied_ports
    
    elif action == 'record':
        # 记录当前进程使用的端口
        pid = os.getpid()

        for attempt in range(50):  # 尝试50次
            try:
                with open(log_file, 'a') as f:
                    f.write(f"{pid},{p_port},{m_port},{dir_parent_1}\n")
                    print(f"已在文件{log_file}记录端口使用: PID={pid}, Python端口={p_port}, MATLAB端口={m_port}")
                break  # 成功写入后退出循环
            except Exception as e:
                if attempt == 49:  # 最后一次尝试失败时打印错误
                    print(f"记录端口使用时出错: {e}")
                time.sleep(0.1)  # 等待0.1秒后重试
        
    return None

# 管理日志文件
def manage_log_file(dir_parent_1, action='init', original_stdout=None, log_handle=None, scene_name=None):
    """
    管理程序日志文件
    
    Args:
        dir_parent_1: 主函数所在目录
        action: 'init'初始化/删除旧日志, 'start'开始记录, 'stop'停止记录
        original_stdout: 原始标准输出（用于stop动作）
        log_handle: 日志文件句柄（用于stop动作）
    """
    log_file = os.path.join(dir_parent_1, "main_print", f"{scene_name}_main_print.log")
    os.makedirs(os.path.dirname(log_file), exist_ok=True)
    
    if action == 'init':
        # 删除旧的日志文件
        if os.path.exists(log_file):
            try:
                os.remove(log_file)
                print(f"已删除旧日志文件: {log_file}")
            except Exception as e:
                print(f"删除旧日志文件时出错: {e}")
        return log_file
    
    elif action == 'start':
        # 开始记录标准输出到日志文件
        try:
            # 将日志文件打开为追加模式
            log_handle = open(log_file, 'a', encoding='utf-8')
            # 保存原始标准输出
            original_stdout = sys.stdout
            # 创建一个同时输出到控制台和文件的类
            class TeeOutput:
                def __init__(self, file, stdout):
                    self.file = file
                    self.stdout = stdout
                    self.file_closed = False
                
                def write(self, data):
                    if not self.file_closed:
                        try:
                            self.file.write(data)
                            self.file.flush()
                        except ValueError:  # 文件已关闭
                            self.file_closed = True
                    self.stdout.write(data)
                    self.stdout.flush()
                
                def flush(self):
                    if not self.file_closed:
                        try:
                            self.file.flush()
                        except ValueError:  # 文件已关闭
                            self.file_closed = True
                    self.stdout.flush()
            
            # 将标准输出重定向到Tee对象
            sys.stdout = TeeOutput(log_handle, original_stdout)
            print(f"开始记录所有输出到日志文件: {log_file}")
            return original_stdout, log_handle
        except Exception as e:
            print(f"开始记录日志时出错: {e}")
            return None, None
    
    elif action == 'stop':
        # 恢复原始标准输出
        if original_stdout:
            # 先恢复原始标准输出，再关闭文件句柄
            temp_stdout = sys.stdout
            sys.stdout = original_stdout
            
            # 关闭文件句柄
            if log_handle:
                try:
                    log_handle.close()
                except Exception:
                    pass  # 忽略关闭文件时的错误
            
            print(f"停止记录日志，日志文件已保存: {log_file}")
    
    return log_file

# 检查是否需要运行gif生成器
def run_gif_generator_if_needed(dir_parent_1, scene_name=None):
    """
    如果配置文件中save_fig_whitout_show为True，则运行gif生成器
    
    Args:
        dir_parent_1: 主函数所在目录
        scene_name: 场景名称，如果提供则只生成该场景的GIF
    """
    try:
        # 读取配置文件
        config_file_path = os.path.join(dir_parent_1, 'inputs', 'test_conf.py')
        if not os.path.exists(config_file_path):
            print(f"未找到配置文件: {config_file_path}")
            return
            
        # 检查配置文件中的设置
        save_fig = False
        with open(config_file_path, 'r', encoding='utf-8') as f:
            content = f.read()
            if "'save_fig_whitout_show':True" in content.replace(" ", ""):
                save_fig = True
                
        # 如果需要保存图片，运行gif生成器
        if save_fig:
            if scene_name:
                # 生成单个场景的GIF
                target_folder = os.path.join(dir_parent_1, 'onsite_images_saved', f'image_{scene_name}')
                print(f"为场景 {scene_name} 生成GIF")
                
                # 使用subprocess运行脚本，传入场景文件夹路径
                gif_generator_path = os.path.join(dir_parent_1, 'onsite_images_saved', 'gif_generateor_single.py')
                if os.path.exists(gif_generator_path):
                    print(f"运行gif生成器: {gif_generator_path}")
                    result = subprocess.run([sys.executable, gif_generator_path, '--folder', target_folder], 
                                          capture_output=True, text=True, check=False)
                    if result.returncode == 0:
                        print("gif生成器运行成功")
                    else:
                        print(f"gif生成器运行失败: {result.stderr}")
                else:
                    print(f"未找到gif生成器: {gif_generator_path}")
            else:
                # 生成所有场景的GIF
                gif_generator_path = os.path.join(dir_parent_1, 'onsite_images_saved', 'gif_generateor_single.py')
                if os.path.exists(gif_generator_path):
                    print(f"运行gif生成器: {gif_generator_path}")
                    # 使用subprocess运行脚本
                    result = subprocess.run([sys.executable, gif_generator_path], 
                                          capture_output=True, text=True, check=False)
                    if result.returncode == 0:
                        print("gif生成器运行成功")
                    else:
                        print(f"gif生成器运行失败: {result.stderr}")
                else:
                    print(f"未找到gif生成器: {gif_generator_path}")
    except Exception as e:
        print(f"运行gif生成器时出错: {e}")

# 解析命令行参数
def parse_command_line_args():
    """
    解析命令行参数
    
    Returns:
        argparse.Namespace: 解析后的命令行参数对象
    """
    parser = argparse.ArgumentParser(description='启动规划器并指定通信端口')
    parser.add_argument('--python-port', type=int, default=25000,
                      help='Python接收端口 (默认: 25000)')
    parser.add_argument('--matlab-port', type=int, default=25001,
                      help='MATLAB接收端口 (默认: 25001)')
    parser.add_argument('--ip', type=str, default='127.0.0.1',
                      help='通信IP地址 (默认: 127.0.0.1)')
    return parser.parse_args()

# 自动查找可用端口
def find_available_ports(args, dir_parent_1):
    """
    自动查找可用的端口组合
    
    Args:
        args: 包含初始端口设置的参数对象
        dir_parent_1: 主函数所在目录
    
    Returns:
        tuple: 找到的可用Python端口和MATLAB端口
    """
    port_retry_count = 0
    max_port_retries = 1000  # 最多尝试次数
    
    # 检查端口日志，避免与其他实例使用相同端口
    occupied_ports = manage_port_usage(dir_parent_1, action='check')
    if occupied_ports:
        print(f"###log### 检测到已有进程占用以下端口: {occupied_ports}")
        # 如果初始端口被占用，自动增加端口号，跳过已占用的端口
        while (args.python_port in occupied_ports or args.matlab_port in occupied_ports) and port_retry_count < max_port_retries:
            # 找到下一个未被占用的端口对
            while args.python_port in occupied_ports or args.matlab_port in occupied_ports:
                args.python_port += 2
                args.matlab_port += 2
                port_retry_count += 1
                if port_retry_count >= max_port_retries:
                    break
            if port_retry_count >= max_port_retries:
                break
            print(f"###log### 端口被占用，自动切换到新端口: Python={args.python_port}, MATLAB={args.matlab_port}")
            
    while port_retry_count < max_port_retries:
        # 打印通信配置信息
        print(f"###log### 尝试使用以下通信配置 (尝试 {port_retry_count + 1}/{max_port_retries}): Python端口: {args.python_port}  MATLAB端口: {args.matlab_port}")
        
        # 检查Python端口是否可用
        python_port_available = check_port_available(args.python_port, args.ip)
        if not python_port_available:
            print(f"###警告### Python端口 {args.python_port} 已被占用!")
            # 将当前被占用的端口添加到occupied_ports列表中
            if args.python_port not in occupied_ports:
                occupied_ports.append(args.python_port)
            if args.matlab_port not in occupied_ports:
                occupied_ports.append(args.matlab_port)
            
            # 增加端口号找下一个可用端口
            args.python_port += 2
            args.matlab_port += 2
            port_retry_count += 1
            
            if port_retry_count >= max_port_retries:
                break
            print(f"###log### 自动切换到新端口: Python={args.python_port}, MATLAB={args.matlab_port}")
            continue
        else:
            print(f"###log### Python端口 {args.python_port} 检查通过，未被占用")
            # 当Python端口可用时，我们就找到了一组可用端口，退出循环
            break
    
    if port_retry_count >= max_port_retries:
        print(f"###错误### 尝试了 {max_port_retries} 次后仍未找到可用端口组合，程序退出")
        sys.exit(1)
    
    # 记录当前使用的端口
    manage_port_usage(dir_parent_1, action='record', p_port=args.python_port, m_port=args.matlab_port)
    
    # MATLAB端口无法从Python直接检查是否被占用，因为它需要在MATLAB端绑定，需要注意这个潜在问题
    print(f"###log### MATLAB端口 {args.matlab_port} 将在MATLAB程序启动时检查")
    
    return args.python_port, args.matlab_port

# 发送终止信号到MATLAB
def send_terminate_signal(ip_address, matlab_port):
    """
    导入socket_module模块并发送终止信号到MATLAB
    
    Args:
        ip_address: 通信IP地址
        matlab_port: MATLAB接收端口
    """
    try:
        # 导入socket_module模块
        socket_module = importlib.import_module("onsite-unstructured.dynamic_scenes.socket_module")
        # 创建客户端并发送终止信号
        client = socket_module.Client(Send_IP=ip_address, Send_Port=matlab_port)
        client.send_terminate_signal()
        client.close_sockets()
        print("###log### 已向MATLAB发送终止信号")
    except Exception as e:
        print(f"###log### 发送终止信号时出错: {e}") 